﻿#pragma once
//HTTP REQUEST请求格式的介绍：
//
//请求行 ：请求方法 空格 URI 空格 HTTP版本 换行符
//请求报头：
//Key : [空格] Value 换行符
//Key : [空格] Value 换行符
//... 换行符
//空行 换行符
//请求正文 DATA
//
//URI：不同于URL，其一般只有路径部分，并且路径部分的第一个分隔符 / 不一定是根目录，其可以是任何目录 叫作web目录，就像我们之前说的，其会指定服务器上的某个资源
//
//HTTP版本：一般指的是客户端的版本 因为客户端发送请求 格式为http / 1.0 http / 1.1 http / 2.0 这种写法
//
//请求正文可以没有
//
//1：如何将报头和有效载荷分离？
//一直读直到空行即可！那data怎么知道读取完毕？请求中的一个选项是Content - Length：xxxx 该字段就是正文的长度，没有正文，则不会存在Content - Length；这件事情不奇怪，因为我们计算器程序就设置了长度属性，位于报头中，只不过http中叫作请求报头中的选项罢了
//
//2：如何分用和交付
//应用层完成 后面讲
//-------------------------------------------- -
//
//-------------------------------------------- -
//HTTP REQUEST应答格式的介绍：
//
//状态行：HTTP版本 空格 状态码 空格 状态码描述 换行符
//响应报头 ：
//Key : [空格] Value 换行符
//Key : [空格] Value 换行符
//... 换行符
//空行 换行符
//响应正文 DATA
//
//响应正文也可以没有 ，响应就是请求中uri所指明的各种类型的资源，
//如：HTML css js 图片 视频等..
//选项可能是都有的，也可能是请求和应答特有的，比如长度就是都有的
//
//状态码：表示请求的状态，一般1xx 2xx 3xx 4xx 5xx 三位数构成
//状态码描述：给用户进行的状态码解释，让其看懂
//所以最经典的就是404 notfound
//
//HTTP版本：服务端的版本
//
//Q：为什么版本会有差距？为什么要有版本字段？
//A：因为即使更新，大家也不一定同时更新，比如微信更新，会多批次递增的推送更新，避免统一更新而版本bug引发问题；游戏内测，手机版本内测都是类似的道理！所以任何软件的更新，不仅我们手机客户端更新，服务器本身也要更新，所以新版本客户端请求就推送到新版本服务器端，老的就推送到老的，版本一致就不会出问题！所以这就是为什么http协议会有版本字段
//-------------------------------------------- -
//
//-------------------------------------------- -
//书写请求协议和响应协议代码
//
//
//一：请求协议
//本质就是一个httprequest类 在类的成员变量去规定协议需要的格式就行
//同理httpresponse类
//
//其次不管是httprequest类还是httpresponse类，肯定都是要有反序列化和序列化功能的，这是毋庸置疑的
//
//-------------------------- -
//请求类的反序列化：
//
//很显然我们需要对接收到的请求进行反序列化 去填充各种成员变量！比如服务端接收到一个客户端传来的请求，此时服务端就应该调用请求类的返序列化去对请求进行拆解
//
//我们以前的反序列化使用JSON相关接口，今天不用了，因为我们http就是不依靠任何第三方库的，我们已经知道了http协议是一行一行的，所以我们只需在反序列化中调用获取单行getoneline接口，不断的获取单行判断即可；
//
//细节在于每获取一行之后，我们要把该行删除，则删除时记得删除尾部的分隔符，也就是我们删除要比截取长一个分隔符的长度 pos + sep即可
//
//而我们成员httprequest类和httpresponse类的成员变量都是已经写好的，所以对于请求类的成员变量请求行我们只需调用一次getoneline接口就行了，而请求报头行数不确定，很简单，我们只需循环调用getoneline接口，直到遇到空行即可！利用http的特性就可以了！
//
//而空行的内容其实就是一个单纯的\r\n。此时的getoneline接口的pos其实就是0了，因为第一个就是分隔符，所以我们当pos为0的时候，也不会冲突，截取0到0，删除0到0 ，都是无影响的，此时对于空行而言截取到的line是空串，所以我们返回sep。其余不是空串的返回line本身就可
//
//总结：
//①：reastr(请求)为空 返回请求本身就是空串
//②：某一行不为空 返回改行line本身
//③：某一行为空 则代表是空行 则返回sep
//
//所以我们在反序列化中 line为空则一定是请求为空 出错了 或者读取完了 line
//不为空 要么就是正常的读取行 要么就是空行
//
//------------------ -
//反序列化总结：
//所以在反序列化中，我们先读取第一行为请求行 然后进入循环
//
//循环得读取请求报头，当读取到的line是空字符串的时候，代表getoneline接口内部出错，则break跳出整个循环，如果读取到分隔符的时候，我们就知道下一行开始就是正文部分了，并且现在已经删除了除正文部分的所用内容，所以什么都不用做，直接赋给成员变量正文即可，因为只剩正文部分了，无需再次剥离；而如果读取到的不是空行，则代表读取到的内容是请求报头，然后继续插入vector
//
//------------------ -
//打印函数：我们把我们请求类对请求进行反序列化之后的成员变量进行打印，和直接打印请求进行对比
//
//打印函数细节在于 需要手动的加换行符 因为我们截取的时候 没有截取 / r / n
//
//现在反序列化可以打印出说到的请求协议的内容了，但是我们想实现想要什么就打印什么的功能 而不是全部请求协议都要打印  ，比如要http版本 要url.....这种位于请求行中的单个属性，这种行为叫作更彻底的序列化
//
//而方法也很简单，因为请求方法 空格 URI 空格 HTTP版本 换行符都在第一行 请求行中 所以我们只需事先把这几个部分存储进哈希表里 然后通过URL等k值 去返回v值即可，该接口叫做parsereqline，而请求行的字段就是空格隔开的，所以依旧是一个截取的思路即可
//
//但是我们还可以用新的方法，stringstream，看似神奇 就是就是默认的按照空格来提取字符；其实cin就是典型的按照空格来提取 所以并不神奇
//
//成员变量请求报头也要如此打散，因为请求报头的每一行都是kv类型的，所以我们期望就是k和v分开，作为哈希表的kv即可！接口叫做parseheader, 内部会调用parseheaderhelper接口，意义在于你给我一行字符串，我按照": "冒号空格去进行kv的拆分，然后把这kv返回出去, 所以外部接口不断范围for读取到一行一行的内容，然后作为参数传递给内部接口，注意，kv得到了，需要makepiar才能插入
//
//区别在于我们的stringstream
//
//打印的时候 打印每一行请求报头的kv时候 用.first 和.second即可
//--------------------------------------
//URL：
//
//当我们没有指明我们要请求什么资源，URL就是一个 / 代表根目录
//否则就是指定的Linux路径 / a / b / c
//
//而当URL是一个 / 或什么都没有的时候(没有指明) 我们作为服务端应该向客户端提供什么？我们不可能把 / 根目录给他，因为根目录是一个目录，不是确切的资源；所以默认行为就是直接拼接上默认的首页链接即可，一般首页文件叫做index.html 或 index.htm
//
//而现在即使知道了首页的文件名字 但是不知道首页文件的路径
//
//其次我们知道 / 根目录是web根目录 ，其次我们知道任何的页面在编码层面都是一个个的文件，所以index.html 或 index.htm就是其中的一个文件。而所有文件就存储在一个目录中，叫作wwwroot目录，也叫做web根目录
//
//所以遇到URL是 / 的时候 就直接打开wwwroot目录下的index.html 或 index.htm，所以任何的网页文件的前缀都是wwwroot，因为其是根目录
//
//所以我们肯定要在所有的URL前加上wwwroot(否则就是Linux下的根目录了，我们的文件都是web根目录中)parsereqline中进行，只需将成员变量URL默认设为wwwroot  然后再parsereqline中读取的时候url += temp即可
//
//这样下来请求的任何路径 反序列化解析之后 都是wwwroot开头
//
//所以这个web根目录是什么目录 完全取决于你自己的设置
//--------------------------------------
//响应协议
//
//所以响应就是把web命令下的资源发回去 只不过需要对资源进行字段封装
//
//现在我们也没对请求做什么 仅仅是反序列化 和 对URL增加前缀  所以下面我们要构建响应协议返回去
//
//首先我们对parsereqline中需要对URL拼接之后的结果进行判断 我们判断最后一个字符 如果是"/"则代表其实wwwroot / 所以我们的响应类的URL拼接上首页链接，反之应该是确切的资源路径
//
//请求类的成员变量的变化，我们引入path让paht在构造中为wwwroot，url是被 += ；path才是正确的路径 清晰的路径 URL是非标准化路径 隐藏了web根目录的
//
//先见一下应答是什么样子 首先我们不想看浏览器解析之后的应答
//telnet指令 访问网址 输入ctrl + 右中括号 即可访问网址 然后回车 开始构建我们的请求 发过去，请求行回车之后 还要输入报头我们没有报头 所以直接再回车 即可获取到响应；此时的响应一定是首页，开发者工具中的代码和这里是一致的 只是优化了，其中就有响应报头空行正文等一系列内容
//
//所以响应类中应该有序列化功能，因为我们只需要实现服务端，所以我们的请求类中只需要反序列化，响应类中只需要序列化，客户端浏览器充当即可
//
//添加构建应答的必要字段  状态码等字段...
//
//注意：我们序列化之后是一个大的字符串 其中我们响应类的成员变量_resp_headers就是响应报头部分，单个类型为string的vector，但是如何填写_resp_headers呢？我们需要从_headers这个unordered_map中去读取kv，然后在kv中加入空格，末尾加入 / r / n 才符合响应报头每一行的格式，然后把所有的字符串通通拼接在一起，不是字符串的就转换为字符串
//
//所以现在的问题在于_headers这个unordered_map如何填充？
//后面解答
//
//现在我们主代码逻辑中 接收到了请求 对请求进行反序列化之后 我们首先应该拿取其中的_path变量，这是资源的路径，也就是得到文件的路径，我们用二进制的形式打开文件 因为文件的类型不确定
//
//读取文件的接口的代码需要deepseek解读一下
//该函数得到正文字符串以及文件大小
//
//而我们打开文件读取到的内容仅仅是有效载荷部分 所以下面我们使用工厂对象构建一个响应对象之后，需要把各个成员变量进行填写，最重要的就是之前的疑问，headers这个unordered_map如何填充？？？
//
//必然需要填写Content - Length: Body的⻓度
//还需填写Content - Type : 数据类型(text / html等)text / html为网页格式写法
//
//我们填写完所有的成员变量之后，我们要调用序列化，然后返回相应
//
//所以下面我们应该实现首页文件，前端代码书写？直接抄写即可，
//w3cschool.html网站中抄写即可
//
//工具：
//①：
//②：
//③：
//
//三个网页，的站内的来回跳转是怎么实现的？很简单通过a标记即可，往大了说就是你点评论区都是同理。每次页面跳转都是http请求，然后得到回应罢了，所以一个网站就是一个wwwroot里面的文件罢了，就是一堆特定目录和文件构成的目录结构，我们写的c++的代码属于后端，负责把特定目录下的特定资源(文件)发送给客户端程序即可  说白了就是一个IO程序
//
//试试图片 因为是超文本协议
//
//图片刷不出来是正常的 因为图片大  浏览器强大才会运气好刷出来 我们的代码不够完善
//
//----------------------------------
//Header的理解：
//
//• Content - Type: 数据类型(text / html等)
//• Content - Length : Body的⻓度
//• Host : 客⼾端告知服务器, 所请求的资源是在哪个主机的哪个端⼝上;
//• User - Agent: 声明⽤⼾的操作系统和浏览器版本信息;
//• Referer : 当前⻚⾯是从哪个⻚⾯跳转过来的;
//• Location : 搭配3xx状态码使⽤, 告诉客⼾端接下来要去哪⾥访问;
//• Cookie : ⽤于在客⼾端存储少量信息.通常⽤于实现会话(session)的功能;
//
//首先不完善的就是不管资源的类型是什么 Content - Type都是text / html，而之前之所有可能刷新出图片 是因为浏览器强大  不能指望浏览器 要自己强大
//
//怎么知道资源的类型？？后缀判断即可，因为客户端的请求会包含访问的资源，所以肯定知道资源的类型，而http协议已经考虑到了这一点了，不同的文件的类型，也就是不同的后缀对应的Content - Type的字段！
//
//叫作Content - Type对照表
//
//所以我们直接在代码中导入我们的对照表，kv类型的unordered_map，然后对资源的后缀进行判断，得到v值
//
//_suffix 为请求类的成员变量 就是后缀 找到最后一个.后面的内容即可，rfind(倒着寻找)；没找到后缀给默认.html  反之后缀为substr的内容
//
//然后主代码中的Content - Type使用kv即可